﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows;

namespace FluentFTP.Model
{
    public class FtpClient
    {
        //private readonly string user;
        //private readonly string password;
        //private readonly string serverUrl;

        public string UserName { get; set; }
        public string Password { get; set; }
        public string Url { get; set; }

        public FtpClient()
        {

        }

        public FtpClient(string user, string password, string serverUrl)
            : this()
        {
            this.UserName = user;
            this.Password = password;
            this.Url = serverUrl;

        }


        public IList<FtpItem> GetDirectoryContents()
        {
            var result = new List<FtpItem>();

            try
            {
                var queryResult = executeQuery(WebRequestMethods.Ftp.ListDirectoryDetails);

                using (StreamReader reader = new StreamReader(queryResult.GetResponseStream()))
                {
                    string line;
                    line = reader.ReadLine();

                    while (line != null)
                    {
                        try
                        {
                            if (line != string.Empty) result.Add(analyzeLine(line, Url));
                        }
                        catch (ArgumentException ex)
                        {
                            Debug.WriteLine("Did not recognize: " + line);
                        }

                        line = reader.ReadLine();
                    }
                }

                return result;
            }
            catch (WebException ex)
            {
                MessageBox.Show("Unable to connect. Check your credentials", "Unable to connect", MessageBoxButton.OK, MessageBoxImage.Error);
                Debug.WriteLine(ex.Message);
                return new List<FtpItem>();
            }
        }

        public void UploadFile(string sourcePath, string targetPath)
        {
            var request = (FtpWebRequest)WebRequest.Create(targetPath.Replace(' ', '_'));
            request.Method = WebRequestMethods.Ftp.UploadFile;
            request.UseBinary = true;

            request.Credentials = new NetworkCredential(UserName, Password);

            byte[] fileContents = getFileBytes(sourcePath);
            request.ContentLength = fileContents.Length;
            try
            {
                using (var requestStream = request.GetRequestStream())
                {
                    requestStream.Write(fileContents, 0, fileContents.Length);
                }

                using (var response = (FtpWebResponse)request.GetResponse())
                {
                    Debug.Write(response.StatusDescription);
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show("Unable to transfer file", "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Debug.WriteLine(ex.Message);
                return;
            }
        }

        public void CreateDirectory(string directoryPath)
        {
            Url = directoryPath;
            executeQuery(WebRequestMethods.Ftp.MakeDirectory);
        }

        private FtpWebResponse executeQuery(string method)
        {
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(Url);
            request.Method = method;
            request.Credentials = new NetworkCredential(UserName, Password);
            request.UseBinary = true;
            return (FtpWebResponse)request.GetResponse();
        }

        private string getFullFilePath(string relativePath)
        {
            return string.Format("{0}//{1}", Url, relativePath);
        }

        public void SaveFile(string fromPath, string toPath)
        {
            var request = new WebClient();
            request.Credentials = new NetworkCredential(UserName, Password);

            request.DownloadFile(fromPath, toPath); // no need to solve encoding issues :-)
        }

        private FtpItem analyzeLine(string line, string url)
        {
            Func<Match, DateTime> getDateTimeIIS = match =>
                {
                    int hours = int.Parse(match.Groups["hour"].Value);
                    if (!string.IsNullOrEmpty(match.Groups["PM"].Value)) hours += 12;
                    return new DateTime(2000 + int.Parse(match.Groups["year"].Value), int.Parse(match.Groups["month"].Value), int.Parse(match.Groups["day"].Value), hours, int.Parse(match.Groups["minute"].Value), 0); // TODO: support for pre-2000 dates
                };

            Func<Match, DateTime> getDateTimeUnix = match =>
                {
                    int monthNumber;
                    int dayNumber;
                    int yearNumber;

                    if (!int.TryParse(match.Groups["day"].Value, out dayNumber)) dayNumber = 1;

                    switch (match.Groups["month"].Value.ToLowerInvariant())
                    {
                        case "jan": monthNumber = 1;
                            break;
                        case "feb": monthNumber = 2;
                            break;
                        case "mar": monthNumber = 3;
                            break;
                        case "apr": monthNumber = 4;
                            break;
                        case "may": monthNumber = 5;
                            break;
                        case "jun": monthNumber = 6;
                            break;
                        case "jul": monthNumber = 7;
                            break;
                        case "aug": monthNumber = 8;
                            break;
                        case "sep": monthNumber = 9;
                            break;
                        case "oct": monthNumber = 10;
                            break;
                        case "nov": monthNumber = 11;
                            break;
                        case "dec": monthNumber = 12;
                            break;
                        default: monthNumber = 1;
                            break;
                    }

                    if (!int.TryParse(match.Groups["year"].Value, out yearNumber)) yearNumber = 1;

                    return new DateTime(yearNumber, monthNumber, dayNumber);
                };

            Regex fileIISRegex = new Regex(@"(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{2})\s+(?<hour>\d{2}):(?<minute>\d{2})(?<PM>PM)?(?<AM>AM)?\s+(?<size>\d+)\s+(?<name>[^\s]+)\s*");
            Regex folderIISRegex = new Regex(@"(?<month>\d{2})-(?<day>\d{2})-(?<year>\d{2})\s+(?<hour>\d{2}):(?<minute>\d{2})(?<PM>PM)?(?<AM>AM)?\s+(?<dir>\<DIR\>)\s+(?<name>[^\s]+)\s*");

            // Courtesy of Yodiz from http://stackoverflow.com/a/2176738/404764
            Regex unixRegex = new Regex(@"^(?<dir>[\-ld])(?<permission>([\-r][\-w][\-xs]){3})\s+(?<filecode>\d+)\s+(?<owner>\w+)\s+(?<group>\w+)\s+(?<size>\d+)\s+(?<timestamp>((?<month>\w{3})\s+(?<day>\d{1,2})\s+(?<hour>\d{1,2}):(?<minute>\d{2}))|((?<month>\w{3})\s+(?<day>\d{1,2})\s+(?<year>\d{4})))\s+(?<name>.+)$");

            Match matchFileIIS = fileIISRegex.Match(line);
            if (matchFileIIS.Length == line.Length) return getFileIIS(url, getDateTimeIIS, matchFileIIS);

            Match matchFolderIIS = folderIISRegex.Match(line);
            if (matchFolderIIS.Length == line.Length) return getFolderIIS(url, getDateTimeIIS, matchFolderIIS);

            Match matchItemUnix = unixRegex.Match(line);
            if (matchItemUnix.Length == line.Length) return getItemUnix(url, getDateTimeUnix, matchItemUnix);

            return new FtpItem();
        }

        private static FtpItem getItemUnix(string url, Func<Match, DateTime> getDateTimeUnix, Match matchItemUnix)
        {
            var name = matchItemUnix.Groups["name"].Value;

            // The arrow is not part of path (no idea what it means), so only the name before the arrow is used.
            if (name.Contains(" ->"))
            {
                Regex nameRegex = new Regex(@"(?<name>[^\/]+)\s\-\>");
                name = nameRegex.Match(name).Groups["name"].Value;
            }

            var item = new FtpItem
            {
                Name = name,
                FullPath = string.Format("{0}/{1}", url, name),
                TimeStamp = getDateTimeUnix(matchItemUnix),
            };

            if (item.Type == FtpItemType.File) item.Size = long.Parse(matchItemUnix.Groups["size"].Value);

            return item;
        }

        private static FtpItem getFolderIIS(string url, Func<Match, DateTime> getDateTimeIIS, Match matchFolderIIS)
        {
            FtpItem file = new FtpItem();
            file.Name = matchFolderIIS.Groups["name"].Value;
            file.FullPath = url + "/" + file.Name;
            file.TimeStamp = getDateTimeIIS(matchFolderIIS);

            return file;
        }

        private static FtpItem getFileIIS(string url, Func<Match, DateTime> getDateTimeIIS, Match matchFileIIS)
        {
            FtpItem file = new FtpItem();
            file.Name = matchFileIIS.Groups["name"].Value;
            file.FullPath = url + "/" + file.Name;
            file.TimeStamp = getDateTimeIIS(matchFileIIS);

            long size = 0;
            long.TryParse(matchFileIIS.Groups["size"].Value, out size);
            file.Size = size;

            return file;
        }

        private byte[] getFileBytes(string path)
        {
            return File.ReadAllBytes(path);
        }
    }
}
